package pers.corvey.crawler.service;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import pers.corvey.crawler.dao.CelebrityDAO;
import pers.corvey.crawler.dao.GenreDAO;
import pers.corvey.crawler.dao.MovieCelebrityDAO;
import pers.corvey.crawler.dao.MovieDAO;
import pers.corvey.crawler.dao.MovieGenreDAO;
import pers.corvey.crawler.exception.DAOException;
import pers.corvey.crawler.extract.MovieExtract;
import pers.corvey.crawler.model.Celebrity;
import pers.corvey.crawler.model.Genre;
import pers.corvey.crawler.model.Movie;
import pers.corvey.crawler.model.MovieCelebrity;
import pers.corvey.crawler.model.MovieGenre;
import pers.corvey.crawler.model.json.MovieResultJson;
import pers.corvey.crawler.transformer.MovieTransformer;

@Service
public class MovieServiceImpl implements MovieService {

	private final static ExecutorService threadpool = Executors.newFixedThreadPool(10);
	
	@Autowired
	private MovieDAO movieDAO;

	@Autowired
	private GenreDAO genreDAO;
	
	@Autowired
	private MovieGenreDAO movieGenreDAO;
	
	@Autowired
	private CelebrityDAO celebrityDAO;
	
	@Autowired
	private MovieCelebrityDAO movieCelebrityDAO;
	
	private interface WayToGetResult {
		MovieResultJson getResultJson(int start) throws IOException;
	}
	
	public List<Movie> crawlBySearch(String keyword, String tag) throws IOException, DAOException {
		return getMultResults((start)->{
			return MovieExtract.findMovies(keyword, tag, start);
		});
	}
	
	public List<Movie> crawlTop250() throws IOException, DAOException {
		return getMultResults((start)->{
			return MovieExtract.getTop250(start);
		});
	}
	
	public List<Movie> crawlComingSoon() throws IOException, DAOException {
		return getMultResults((start)->{
			return MovieExtract.getComingSoon(start);
		});
	}
	
	public List<Movie> crawlInTheaters(String city) throws IOException, DAOException {
		return getMultResults((start)->{
			return MovieExtract.getInTheaters(city, start);
		});
	}
	
	private List<Movie> getMultResults(WayToGetResult way) throws IOException, DAOException {
		List<Movie> ret = new LinkedList<>();
		MovieResultJson json = way.getResultJson(0);
		List<Movie> movies = MovieTransformer.toMovie(json.getSubjects());
		saveAll(movies);
		ret.addAll(movies);
		List<Future<List<Movie>>> futures = new LinkedList<>();
		for (int i=json.getCount(); i<json.getTotal(); i+=json.getCount()) {
			final int start = i;
			futures.add(threadpool.submit(() -> {
				try {
					MovieResultJson _json = way.getResultJson(start);
					List<Movie> _movies = MovieTransformer.toMovie(_json.getSubjects()); 
					return saveAll(_movies);
				} catch (Exception e) {
					e.printStackTrace();
					return null;
				}
			}));
		}
		for (Future<List<Movie>> future : futures) {
			try {
				movies = future.get();
				if (movies != null) {
					ret.addAll(movies);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if (json.getTotal() != 0 && movies.size() == 0) {
			throw new DAOException();
		}
		return ret;
	}
	
	private Movie save(Movie movie) throws Exception {
		if (movie == null) {
			return null;
		}
		movieDAO.save(movie);
		List<Celebrity> celebrities = movie.getCasts(); 
		if (celebrities != null && celebrities.size() != 0) {
			celebrityDAO.saveAll(celebrities);
			List<MovieCelebrity> casts = MovieCelebrity.create4Cast(movie);
			movieCelebrityDAO.saveAll(casts);
		}
		celebrities = movie.getDirectors();
		if (celebrities != null && celebrities.size() != 0) {
			celebrityDAO.saveAll(celebrities);
			List<MovieCelebrity> directors = MovieCelebrity.create4Director(movie);
			movieCelebrityDAO.saveAll(directors);
		}
		saveGenre(movie);
		return movie;
	}
	
	private List<Movie> saveAll(List<Movie> movies) {
		if (movies != null) {
			for (Movie movie : movies) {
				try {
					save(movie);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return movies;
	}
	
	private void saveGenre(Movie movie) throws Exception {
		List<Genre> genres = movie.getGenres(); 
		if (genres == null || genres.size() == 0) {
			return ;
		}
		List<Genre> newGenre = new LinkedList<>();
		for (Genre genre : movie.getGenres()) {
			String id = genreDAO.findIdByName(genre.getName()); 
			if (id == null) {
				newGenre.add(genre);
			} else {
				genre.setId(id);
			}
		}
		if (newGenre.size() != 0) {
			genreDAO.saveAll(newGenre);
		}
		
		List<MovieGenre> mgs = MovieGenre.createMovieGenres(movie);
		movieGenreDAO.saveAll(mgs);
	}
}
